package com.ubing.gxspringboot.gxapplicationcontext;

import com.ubing.gxspringboot.gxannotation.*;
import com.ubing.gxspringboot.gxproxy.AOPProxy;
import com.ubing.gxspringboot.gxproxy.CGLibProxy;
import lombok.Data;
import lombok.ToString;

import javax.servlet.ServletOutputStream;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

@Data
@ToString
public class GXWebApplicationContext {

    private HashMap<String,Object> beanIoc=new HashMap<>();

    private ArrayList<String> packageIoc=new ArrayList<>();


    private HashMap<Method,Object> webIoc=new HashMap<>();

    private HashMap<String,Object> pathIoc=new HashMap<>();

    public void init(Class<?> clazz) {
        // 1、扫描
        scanPackage(clazz);
        // 2、初始化bean
        initBeans();
        // 3、初始化代理对象
        initProxyAOP(clazz);
        // 4、注入属性
        autowired();
        // 5、结束
        beanIoc.forEach((k,v)->{
            System.out.println("ioc容器实例\t"+v.getClass());
        });
        System.out.println();
        System.out.println("build ioc success");
    }

    private void initProxyAOP(Class<?> clazz) {
        Package aPackage = clazz.getPackage();
        String packageName = aPackage.getName();
        // 使用类加载器获取资源路径
        ClassLoader classLoader = clazz.getClassLoader();
        URL packageUrl = classLoader.getResource(packageName.replace(".", "/"));
        if (packageUrl != null) {
            try {
                String decodedPath = URLDecoder.decode(packageUrl.getFile(), String.valueOf(StandardCharsets.UTF_8));
                File packageDir = new File(decodedPath);
                scanAOPFiles(packageDir);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        } else {
            System.out.println("Package directory not found.");
        }

    }

    public void scanAOPFiles(File dir) {
        if (dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isFile()) {
                        if (file.getName().endsWith(".class")) {
                            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                            String classPath = classLoader.getResource("").getPath();
                            try {
                                String decodedPath = URLDecoder.decode(classPath, String.valueOf(StandardCharsets.UTF_8));
                                String substring = decodedPath.replace("/", "\\").substring(1);
                                String className = file.getPath().replace(".class", "").replace(substring,"").replace("\\",".");
                                Class<?> aClass = Class.forName(className);
                                if(aClass.isAnnotation()) continue;
                                if(hasGXComponent(aClass.getAnnotations())) {
                                    if(aClass.getDeclaredAnnotation(GXAspect.class)!=null){
                                        Method[] declaredMethods = aClass.getDeclaredMethods();
                                        for (Method declaredMethod : declaredMethods) {
                                            if(declaredMethod.getDeclaredAnnotation(GXAround.class)!=null){
                                                GXAround gxAround = declaredMethod.getDeclaredAnnotation(GXAround.class);
                                                String value = gxAround.value();
                                                String methodName = value.replace("()", "");
                                                Method method = aClass.getDeclaredMethod(methodName);
                                                GXPointcut gxPointcut = method.getAnnotation(GXPointcut.class);
                                                String proxyPath = gxPointcut.value();
                                                for (String path : packageIoc) {
                                                   if(path.startsWith(proxyPath)){
                                                       Class<?> forName = Class.forName(path);
                                                       Method[] forNameDeclaredMethods = forName.getDeclaredMethods();
                                                       Class<?>[] interfaces = forName.getInterfaces();
                                                       Object o = forName.newInstance();
                                                       Object bind;
                                                       boolean flag=true;
                                                       if(interfaces.length!=forNameDeclaredMethods.length) flag=false;
                                                       if(flag) bind = new AOPProxy().bind(o,declaredMethod.getName(),aClass);// JDK
                                                       else bind  = CGLibProxy.bind(o,declaredMethod.getName(),aClass);// CGLIB
                                                       setBean(toLowerFirstCase(forName.getSimpleName()),bind);
                                                   }
                                                }
                                            }
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                    } else if (file.isDirectory()) {
                        scanAOPFiles(file); // 递归调用，扫描子目录
                    }
                }
            }
        }
    }

    private void scanPackage(Class<?> clazz) {
        Package aPackage = clazz.getPackage();
        String packageName = aPackage.getName();
        // 使用类加载器获取资源路径
        ClassLoader classLoader = clazz.getClassLoader();
        URL packageUrl = classLoader.getResource(packageName.replace(".", "/"));
        if (packageUrl != null) {
            try {
                String decodedPath = URLDecoder.decode(packageUrl.getFile(), String.valueOf(StandardCharsets.UTF_8));
                File packageDir = new File(decodedPath);
                scanFiles(packageDir);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        } else {
            System.out.println("Package directory not found.");
        }
    }

    public void scanFiles(File dir) {
        if (dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isFile()) {
                        if (file.getName().endsWith(".class")) {
                            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                            String classPath = classLoader.getResource("").getPath();
                            try {
                                String decodedPath = URLDecoder.decode(classPath, String.valueOf(StandardCharsets.UTF_8));
                                String substring = decodedPath.replace("/", "\\").substring(1);
                                String className = file.getPath().replace(".class", "").replace(substring,"").replace("\\",".");
                                packageIoc.add(className);
                                Class<?> aClass = Class.forName(className);
                                if(aClass.isAnnotation()) continue;
                                if(hasGXComponent(aClass.getAnnotations())) {
                                    Object instance = aClass.newInstance();
                                    setBean(toLowerFirstCase(aClass.getSimpleName()),instance);
                                    if(aClass.getAnnotation(GXRestController.class)!=null){
                                        Method[] declaredMethods = aClass.getDeclaredMethods();
                                        for (Method declaredMethod : declaredMethods) {
                                            GXRequestMapping annotation = declaredMethod.getAnnotation(GXRequestMapping.class);
                                            if(annotation!=null){
                                                if (pathIoc.containsKey(annotation.value())) throw new Exception("路径重复");
                                                pathIoc.put(annotation.value(),declaredMethod);
                                                webIoc.put(declaredMethod,instance);
                                            }
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                    } else if (file.isDirectory()) {
                        scanFiles(file); // 递归调用，扫描子目录
                    }
                }
            }
        }
    }

    private  static ArrayList<Class> annotationArrayList=new ArrayList<>(Arrays.asList(new Class[]{Target.class, Retention.class, Documented.class, Inherited.class}));

    private static boolean hasGXComponent(Annotation[] annotations){
        for (Annotation component : annotations) {
            if (component.annotationType().equals(GXComponent.class)) {
                return true;
            }else if(!annotationArrayList.contains(component.annotationType())) return hasGXComponent(component.annotationType().getAnnotations());
        }
        return false;
    }

    private static String toLowerFirstCase(String beanName){
        char[] charArray = beanName.toCharArray();
        charArray[0]+=32;
        return String.valueOf(charArray);
    }


    private void initBeans() {
    }

    private void autowired() {
        beanIoc.forEach((key,value)->{
            for (Field declaredField : value.getClass().getDeclaredFields()) {
                GXAutowired autowired = declaredField.getAnnotation(GXAutowired.class);
                if(autowired!=null){
                    try {
                        declaredField.setAccessible(true);
                        if (declaredField.getName().equals("gxWebApplicationContext")) {
                            declaredField.set(value,this);
                        }else {
                            declaredField.set(value,getBean(declaredField.getName()));
                        }
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
    }

    public Object getBean(String beanName) {
        return beanIoc.get(beanName);
    }

    public <T> T getBean(String beanName, Class<T> clazz) {
        return (T)beanIoc.get(beanName);
    }

    public void setBean(String beanName,Object bean) {
        beanIoc.put(beanName,bean);
    }

}
